﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using epk23.PartIIProject.Exceptions;
using System.Threading;
using System.Windows.Threading;

namespace epk23.PartIIProject
{
    class Interpreter
    {
        public MachineWindow machineWindow;
        public Machine machine { get; set; }
        public SpeedManager speedManager { get; set; }
        public Semaphore semStep;
        public Boolean running { get; set; }

        public Thread thread;

        private int speed;

        private Stack<Context> stack;


        public delegate void delExecutionFinished();
        public delegate void delRuleExecuted();
        public delegate void delRuleExecuting(Rule rule);

        public Interpreter()
        {

            semStep = new Semaphore(1, 1);
        }

        public void run(Machine m)
        {
            this.machine = m;

            //System.Console.WriteLine(m.myCounter);

            stack = new Stack<Context>();

            thread = (new Thread(new ThreadStart(runThread)));
            thread.Start();
            running = true;
        }

        public void runThread()
        {
            Rule currentRule;
            Rule nextRule = null;
            Type currentRuleType;
            String targetRegisterName;
            Register targetRegister;
            int incrTargetRuleNum;
            int decrTargetRuleNum1;
            int decrTargetRuleNum2;
            int nextRuleNumber;

            int sleepTime = 0;


            //m.registers = new List<Register>(args.Length);

            //int i = 1;
            //foreach(int arg in args)
            //{
            //m.registers.Add(new Register(i.ToString(), args[i-1]));
            //i++;
            //}

            currentRule = machine.lookupRule(machine.startRuleNumber); //load first rule (0)

            //beginning rule execution
            //delRuleExecuted ruleExecutedHandler = ruleExecuted;
            //machineWindow.Dispatcher.Invoke(ruleExecutedHandler, null);

            while (true)
            {

                speed = speedManager.Speed;

                switch (speed)
                {
                    case 1:
                        sleepTime = 1000;
                        break;
                    case 2:
                        sleepTime = 500;
                        break;
                    case 3:
                        sleepTime = 100;
                        break;
                }

                if (speed == 0)
                {
                    speedManager.Waiting = true;
                    semStep.WaitOne();
                    speedManager.Waiting = false;
                }

                else System.Threading.Thread.Sleep(sleepTime);

                currentRuleType = currentRule.GetType();

                //if (currentRuleType != typeof(HaltRule))
                //    ((PrintableRule)currentRule).printRule();

                if (stack.Count == 0)
                {
                    delRuleExecuting ruleExecutingHandler = ruleExecuting;
                    machineWindow.Dispatcher.Invoke(ruleExecutingHandler, currentRule);
                }

                if (currentRuleType == typeof(IncrementRule) || currentRuleType == typeof(DecrementRule))
                {
                    if (currentRuleType == typeof(IncrementRule))
                    {
                        targetRegisterName = currentRule.targetRegister;
                        //machine.addRegister(targetRegisterName);

                        incrTargetRuleNum = ((IncrementRule)currentRule).targetRuleNumber;

                        targetRegister = machine.lookupRegister(targetRegisterName);
                        targetRegister.increment();

                        nextRuleNumber = incrTargetRuleNum;

                        nextRule = machine.lookupRule(nextRuleNumber);
                    }
                    else if (currentRuleType == typeof(DecrementRule))
                    {
                        targetRegisterName = currentRule.targetRegister;
                        //machine.addRegister(targetRegisterName);
                        decrTargetRuleNum1 = ((DecrementRule)currentRule).targetRuleNumber;
                        decrTargetRuleNum2 = ((DecrementRule)currentRule).targetRuleNumber2;

                        targetRegister = machine.lookupRegister(targetRegisterName);
                        if (targetRegister.value == 0)
                        {
                            nextRuleNumber = decrTargetRuleNum2;
                        }
                        else
                        {
                            targetRegister.decrement();
                            nextRuleNumber = decrTargetRuleNum1;
                        }

                        nextRule = machine.lookupRule(nextRuleNumber);
                    }
                    currentRule = nextRule;
                }
                else if (currentRuleType == typeof(MachineRule)) //MachineRule
                {
                    int PC = ((MachineRule)currentRule).targetRuleNumber;
                    Context currentContext = new Context(machine, PC);
                    stack.Push(currentContext);

                    Machine nextMachine = FileManager.readFile(((MachineRule)currentRule).machineName).Machine;
                    nextMachine.registers = machine.registers;

                    machine = nextMachine;

                    currentRule = machine.lookupRule(machine.startRuleNumber);
                }
                else // HaltRule
                {
                    if (stack.Count == 0)
                        break;
                    else
                    {
                        Context newContext = stack.Pop();
                        machine = newContext.Machine;
                        currentRule = machine.lookupRule(newContext.PC);
                    }
                }

                delRuleExecuted ruleExecutedHandler = ruleExecuted;
                machineWindow.Dispatcher.Invoke(ruleExecutedHandler, null);
            }

            //machine.registers.Sort(Machine.registerComparison);

            machine.printRegisters();
            running = false;

            delExecutionFinished executionFinishedHandler = executionFinished;
            machineWindow.Dispatcher.Invoke(executionFinishedHandler, null);
        }

        private void ruleExecuting(Rule rule)
        {
            machineWindow.ruleExecuting(rule);
        }

        private void ruleExecuted()
        {
            machineWindow.ruleExecuted();
        }

        private void executionFinished()
        {
            machineWindow.executionFinished();
        }
    }
}
